package com.ks.winterwarmimage.mqtt;

import android.annotation.SuppressLint;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Binder;
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.util.Log;

import com.ks.winterwarmimage.mqtt.service.MqttAndroidClient;
import com.ks.winterwarmimage.utils.BasicsUtil;

import org.eclipse.paho.client.mqttv3.IMqttActionListener;
import org.eclipse.paho.client.mqttv3.IMqttAsyncClient;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.IMqttToken;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.eclipse.paho.client.mqttv3.MqttToken;

import java.io.UnsupportedEncodingException;
import java.util.UUID;

public  class MsgMqttService extends Service {
    private static final String TAG = MsgMqttService.class.getSimpleName();

    private static MsgMqttService mMsgMqttService;
    private static MqttConnectOptions mMqttConnectOptions;
    /**默认登录用户*/
    public final static String DEF_MQTT_USER= "esp32_app";
    /**默认登录密码*/
    public final static String DEF_MQTT_PWD="ks666888";
    public static String PUBLISH_TOPIC  = "tourist_enter";//发布主题
    public static String RESPONSE_TOPIC = "message_arrived";//响应主题
    /**绑定服务成功*/
    public final static String BIN_SERVICE_SUCCESS  = "com.nursing.lamp.mqtt.BIN_SERVICE_SUCCESS";
    /**连接MQTT服务端*/
    public final static String MQTT_CONNECT  = "com.nursing.lamp.mqtt.MQTT_CONNECT";
    /**断开连接*/
    public final static String MQTT_DISCONNECT = "com.nursing.lamp.mqtt.MQTT_DISCONNECT";
    /**连接MQTT服务端成功*/
    public final static String MQTT_CONNECT_SUCCESS = "com.nursing.lamp.mqtt.MQTT_CONNECT_SUCCESS";
    /**连接MQTT服务端失败*/
    public final static String MQTT_CONNECT_FAIL = "com.nursing.lamp.mqtt.MQTT_CONNECT_FAIL";

    /**收到MQTT消息*/
    public final static String MQTT_MESSAGE_ARRIVED="com.nursing.lamp.mqtt.MQTT_MESSAGE_ARRIVED";

    /**广播通知绑定*/
    private final IBinder mBinder = new LocalBinder();

    private static MqttAndroidClient mqttAndroidClient;
    /**用户断开*/
    private static boolean userDisconnect;
    // @RequiresApi(api = 26)
//  private static String CLIENTID = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ? Build.getSerial() : Build.SERIAL;//客户端ID，一般以客户端唯一标识符表示，这里用设备序列号表示
    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }
    @Override
    public void onDestroy() {
        mqttDisconnect();
        super.onDestroy();
    }
    /**
     * 绑定服务
     * @param context 上下文
     * @param filter 消息过滤器
     * @param broadcastReceiver 消息回调
     */
    public static void bindService(Context context, IntentFilter filter, BroadcastReceiver broadcastReceiver){
        Intent mqttServiceIntent = new Intent(context, MsgMqttService.class);
        //context.startService(new Intent(context, MsgMqttService.class));
        context.bindService(mqttServiceIntent,mServiceConnection,BIND_AUTO_CREATE);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            context.registerReceiver(broadcastReceiver,filter,RECEIVER_EXPORTED);
        }else{
            context.registerReceiver(broadcastReceiver,filter);
        }
        //context.registerReceiver(broadcastReceiver,filter);
    }

    /**
     * 注销服务
     * @param context 上下文
     * @param broadcastReceiver 消息回调
     */
    public static void unBindService(Context context,BroadcastReceiver broadcastReceiver){
        try {
            if(mMsgMqttService!=null) {
                if(mServiceConnection!=null)
                    context.unbindService(mServiceConnection);
                context.unregisterReceiver(broadcastReceiver);
                mMsgMqttService = null;
            }
        } catch (Exception e) {
            e.printStackTrace();
            Log.e(TAG,e.getMessage());
        }
    }

    /**
     * 注销当前广播接收
     * @param context 上下文
     * @param broadcastReceiver
     */
    public static void unRegisterReceiver(Context context,BroadcastReceiver broadcastReceiver){
        if(mMsgMqttService!=null) {
            context.unregisterReceiver(broadcastReceiver);
        }
    }
    /**管理服务生命周期**/
    private static final ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mMsgMqttService= ((LocalBinder) service).getService();
            if(mMsgMqttService!=null) {
                broadcastUpdate(BIN_SERVICE_SUCCESS, null, null);
            }
        }
        @Override
        public void onServiceDisconnected(ComponentName name) {
            mMsgMqttService=null;
        }
    };
    /**
     * 订阅主题，其中可能包含通配符。
     * @param topic 要订阅的主题，可以包含通配符。
     * @param qos 订阅的最大服务质量。以较低服务质量发布的消息将以发布的QoS接收。以更高服务质量发布的消息将使用订阅上指定的QoS来接收。
     * @return 用于跟踪并等待订阅完成的令牌。如果设置了，则令牌将传递给回调方法。如果为null 表示出错
     */
    public static IMqttToken subscribe(String topic,int qos){
        try {
            if(mqttAndroidClient!=null && isConnectIsNomarl() && mqttAndroidClient.isConnected()) {
                return  mqttAndroidClient.subscribe(topic, qos);
            }
        } catch (MqttException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 订阅多个主题，每个主题可能包含通配符。
     * <p>
     * 与单独订阅每个主题相比，提供了一种订阅多个主题的优化方式。
     *</p>
     * @param topic 要订阅的一个或多个主题，其中可以包含通配符
     * @param qos 订阅的最高服务质量。以较低服务质量发布的消息将以发布的QoS接收。以更高服务质量发布的消息将使用订阅上指定的QoS来接收。
     * @return 用于跟踪并等待订阅完成的令牌。如果设置了，则令牌将传递给回调方法。如果未null 至表示出错
     */
    public static IMqttToken subscribe(String[] topic,int[] qos){
        try {
            if(mqttAndroidClient!=null && isConnectIsNomarl() && mqttAndroidClient.isConnected()) {
                return mqttAndroidClient.subscribe(topic, qos);
            }
        } catch (MqttException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 请求服务器从主题中取消订阅客户端
     * @param topic 要取消订阅的主题。它必须与先前订阅中指定的主题匹配。
     * @return 用于跟踪并等待取消订阅完成的令牌。如果设置了，则令牌将传递给回调方法。null 错误
     */
    public static IMqttToken unsubscribe(String topic){
        try {
            if(mqttAndroidClient!=null && isConnectIsNomarl() && mqttAndroidClient.isConnected()) {
                return mqttAndroidClient.unsubscribe(topic);
            }
        } catch (MqttException e) {
            e.printStackTrace();
        }
        return null;
    }
    /**
     * 请求服务器从一个或多个主题中取消订阅客户端。
     * @param topics 要取消订阅的一个或多个主题。每个主题必须与先前订阅中指定的主题匹配。
     * @return 用于跟踪并等待取消订阅完成的令牌。如果设置了，则令牌将传递给回调方法。null 错误
     */
    public static IMqttToken unsubscribe(String[] topics){
        try {
            if(mqttAndroidClient!=null && isConnectIsNomarl() && mqttAndroidClient.isConnected()) {
                return mqttAndroidClient.unsubscribe(topics);
            }
        } catch (MqttException e) {
            e.printStackTrace();
        }
        return null;
    }
    /**
     *  请求服务器从主题中取消订阅客户端。
     * @param topic 要取消订阅的主题。它必须与先前订阅中指定的主题匹配。
     * @param userContext 用于向回调传递上下文的可选对象。如果不需要，请使用null。
     * @param callback 可选的侦听器，当取消订阅完成时将收到通知
     * @return 用于跟踪并等待取消订阅完成的令牌。如果设置了，则令牌将传递给回调方法。null 错误
     */
    public static IMqttToken unsubscribe(String topic,Object userContext, IMqttActionListener callback ){
        try {
            if(mqttAndroidClient!=null && isConnectIsNomarl() && mqttAndroidClient.isConnected()) {
                return mqttAndroidClient.unsubscribe(topic,userContext,callback );
            }
        } catch (MqttException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 请求服务器从一个或多个主题中取消订阅客户端。
     * <p>Unsubcribing与subscribing是相反的。当服务器接收到取消订阅请求时，它会查看是否可以找到与客户端匹配的订阅，然后将其删除。在此之后，服务器将不再为此订阅向客户端发送消息。</p>
     * <p>取消订阅上指定的主题必须与原始订阅请求中指定的主题匹配，取消订阅才能成功</p>
     * <p>该方法在取消订阅完成之前返回控件。可通过以下方式跟踪完成情况：</p>
     * <ul>
     * <li>等待返回的令牌{@link MqttToken#waitForCompletion()}</li>
     * <li>或将回调{@link IMqttActionListener}传递到此方法</li>
     * </ul>
     * @param topics 要取消订阅的一个或多个主题。每个主题必须与先前订阅中指定的主题匹配。
     * @param userContext 用于向回调传递上下文的可选对象。如果不需要，请使用null。
     * @param callback 可选的侦听器，取消订阅完成时将收到通知
     * @return 用于跟踪并等待取消订阅完成的令牌。如果设置了，则令牌将传递给回调方法。null 错误
     */
    public static IMqttToken unsubscribe(String[] topics, Object userContext,IMqttActionListener callback){
        try {
            if(mqttAndroidClient!=null && isConnectIsNomarl() && mqttAndroidClient.isConnected()) {
                return mqttAndroidClient.unsubscribe(topics,userContext,callback );
            }
        } catch (MqttException e) {
            e.printStackTrace();
        }
        return null;
    }
    /**
     *  将消息发布到服务器上的主题。获取{@link MqttMessage}消息并以请求的服务质量将其传递给服务器。
     * @param topic 传递给的主题名称，例如“finance/stock/ibm”。
     * @param message 要传递的消息
     * @return 成功返回用于跟踪并等待发布完成的令牌。令牌将传递给任何已设置的回调；失败null
     */
    public static IMqttDeliveryToken publish(String topic,MqttMessage message) {
        try {
            if(mqttAndroidClient!=null && isConnectIsNomarl() && mqttAndroidClient.isConnected()) {
               String hexStr= BasicsUtil.bytesToHexString(message.getPayload());
               Log.e(TAG,"publish: "+hexStr);
                return mqttAndroidClient.publish(topic, message);
            }
        } catch (MqttException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 将消息发布到服务器上的主题。
     *<p> 一种方便的方法，它将使用字节数组负载和指定的QoS创建一个新的{@link MqttMessage}对象，然后发布它。</p>
     * @param topic 传递给的主题名称，例如“finance/stock/ibm”。
     * @param payload 要用作有效负载的字节数组
     * @param qos 传递消息的服务质量。有效值为0、1或2。
     * @param retained 服务器是否应保留此消息。
     * @return 成功返回用于跟踪并等待发布完成的令牌。令牌将传递给任何已设置的回调；失败null
     */
    public static IMqttDeliveryToken publish(String topic,byte[] payload,int qos,boolean retained) {
        try {
            if(mqttAndroidClient!=null && isConnectIsNomarl() && mqttAndroidClient.isConnected()) {
                //参数分别为：主题、消息的字节数组、服务质量、是否在服务器保留断开连接后的最后一条消息
                String hexStr= BasicsUtil.bytesToHexString(payload);
                Log.e(TAG,"publish: "+hexStr);
                return mqttAndroidClient.publish(topic, payload,qos,retained);
            }
        } catch (MqttException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 将消息发布到服务器上的主题。
     * <p>一旦此方法干净地返回，消息就被客户机接受发布，并将在后台线程上传递。如果连接失败或客户端停止，则在以下条件下，一旦与服务器重新建立连接，将向请求的服务质量传递消息：</p>
     * <ul>
     *     <li>使用相同的clientID重新建立连接</li>
     *     <li>原始连接是在{@link MqttConnectOptions#setCleanSession(boolean)}设置为false的情况下建立的</li>
     *     <li>重新建立连接时{@link MqttConnectOptions#setCleanSession（boolean）}设置为false</li>
     *     <li>根据故障发生的时间，QoS 0消息可能无法传递。</li>
     * </ul>
     * <p>在构建应用程序时，主题树的设计应考虑主题名称语法和语义的以下原则：</p>
     *<nl>
     *     <li>主题必须至少有一个字符长。</li>
     *     <li>主题名称区分大小写。例如，帐户和帐户是两个不同的主题。</li>
     *     <li>主题名称可以包含空格字符。例如，应付账款是一个有效的主题。</li>
     *     <li>前导“/”创建一个独特的主题。例如，/finance不同于finance。/财务匹配“+/+”和“/+”，但不匹配“+”。</li>
     *     <li>不要在任何主题中包含空字符（Unicode\x0000）。</li>
     *</nl>
     * <p>以下原则适用于主题树的构造和内容：</p>
     * <ul>
     *     <li>长度限制为64k，但在此范围内，主题树中的级别数没有限制。</li>
     *     <li>可以有任意数量的根节点；也就是说，可以有任意数量的主题树。</li>
     * </ul>
     * <p>在控制发布完成之前返回方法。可通过以下方式跟踪完成情况：</p>
     * <nl>
     *     <li>设置{@link IMqttAsyncClient#setCallback(MqttCallback)}</li>
     *     <li>{@link MqttCallback#deliveryComplete(IMqttDeliveryToken)}方法将被调用。</li>
     *     <li>等待返回的令牌{@link MqttToken#waitForCompletion()}或</li>
     *     <li>将回调{@link IMqttActionListener}方法传入</li>
     * </nl>
     * @param topic 传递给的主题名称，例如“finance/stock/ibm”。
     * @param message 传送到服务器的消息
     * @param userContext 用于向回调传递上下文的可选对象。如果不需要，请使用null。
     * @param callback 可选的侦听器，当消息传递完成并达到所请求的服务质量时将收到通知
     * @return 用于跟踪并等待发布完成的令牌。如果设置了，则令牌将传递给回调方法；null 出错。
     */
    public static IMqttDeliveryToken publish(String topic, MqttMessage message, Object userContext, IMqttActionListener callback){
        try {
            if(mqttAndroidClient!=null && isConnectIsNomarl() && mqttAndroidClient.isConnected()) {

                return mqttAndroidClient.publish(topic, message,userContext,callback);
            }
        } catch (MqttException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 将消息发布到服务器上的主题。
     *<p>一种方便的方法，它将创建一个新的{@link MqttMessage}对象，该对象具有字节数组负载、指定的QoS和保留，然后发布它。</p>
     * @param topic 传递给的主题名称，例如“finance/stock/ibm”。
     * @param payload 要用作有效负载的字节数组
     * @param qos 传递消息的服务质量。有效值为0、1或2。
     * @param retained 服务器是否应保留此消息。
     * @param userContext 用于向回调传递上下文的可选对象。如果不需要，请使用null。
     * @param callback 可选的侦听器，当消息传递完成并达到所请求的服务质量时将收到通知
     * @return 用于跟踪并等待发布完成的令牌。令牌将传递给任何已设置的回调；null 出错。
     *  @see #publish(String, MqttMessage, Object, IMqttActionListener)
     */
    public static IMqttDeliveryToken publish(String topic, byte[] payload, int qos,  boolean retained, Object userContext, IMqttActionListener callback){
        try {
            if(mqttAndroidClient!=null && isConnectIsNomarl() && mqttAndroidClient.isConnected()) {
                return mqttAndroidClient.publish(topic, payload,qos,retained,userContext,callback);
            }
        } catch (MqttException e) {
            e.printStackTrace();
        }
        return null;
    }
    /**
     * 响应 （收到其他客户端的消息后，响应给对方告知消息已到达或者消息有问题等）
     *
     * @param message 消息
     */
    public static void response(String message) {
        String topic = RESPONSE_TOPIC;
        Integer qos = 2;
        Boolean retained = false;
        try {
            //参数分别为：主题、消息的字节数组、服务质量、是否在服务器保留断开连接后的最后一条消息
            mqttAndroidClient.publish(topic, message.getBytes(), qos.intValue(), retained.booleanValue());
        } catch (MqttException e) {
            e.printStackTrace();
        }
    }

    /**
     * 连接到MQTT服务
     * @param addr 服务端ip地址
     * @param port 服务端tcp端口号
     * @param userName 用户名称
     * @param pwd 用户密码
     */
    public static void mqttConnect(String addr,int port,String userName,String pwd){
        String serverURI="tcp://"+addr+":"+port;
        mqttConnect(serverURI,userName,pwd);
    }
    /**
     * 连接到MQTT服务
     * @param serverURI 服务器地址。格式为："tcp://ip地址:端口号"
     * @param userName 用户名称
     * @param pwd 用户密码
     */
    public static void mqttConnect( String serverURI,String userName,String pwd){
        mqttDisconnect();
        userDisconnect=false;
        String CLIENT_ID= getUniquePseudoID();//UUID.randomUUID().toString();// = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ? Build.getSerial() : Build.SERIAL;//客户端ID，一般以客户端唯一标识符表示，这里用设备序列号表示
        mqttAndroidClient = new MqttAndroidClient(mMsgMqttService, serverURI, CLIENT_ID);
        mqttAndroidClient.setCallback(mqttCallback); //设置监听订阅消息的回调
        mMqttConnectOptions = new MqttConnectOptions();
        mMqttConnectOptions.setCleanSession(true); //设置是否清除缓存
        mMqttConnectOptions.setConnectionTimeout(10); //设置超时时间，单位：秒
        mMqttConnectOptions.setKeepAliveInterval(20); //设置心跳包发送间隔，单位：秒
        try {
            mMqttConnectOptions.setWill("finalTopic","Keep the last message".getBytes("utf-8"),1,false);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        if(userName!=null && userName.length()>0) {
            mMqttConnectOptions.setUserName(userName); //设置用户名
        }
        if(pwd!=null && pwd.length()>0) {
            mMqttConnectOptions.setPassword(pwd.toCharArray()); //设置密码
        }
        doClientConnection();
    }

    /**
     * 断开连接
     */
    public static void mqttDisconnect(){
        try {
            if(mqttAndroidClient!=null) {
                if(isConnectIsNomarl()&& mqttAndroidClient.isConnected()) {
                    mqttAndroidClient.disconnect();
                }
                userDisconnect=true;
            }
        } catch (MqttException e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取mqtt连接状态
     * @return true 连接；false 未连接
     */
    public static boolean getMqttConnect(){
        if(mqttAndroidClient!=null) {
            if(isConnectIsNomarl()&& mqttAndroidClient.isConnected()) {
                return true;
            }
        }
        return false;
    }

    /**
     * 连接MQTT服务器
     */
    private static void doClientConnection() {
        if (!userDisconnect && !mqttAndroidClient.isConnected() && isConnectIsNomarl()) {
            try {
                mqttAndroidClient.connect(mMqttConnectOptions, null, iMqttActionListener);
                broadcastUpdate(MQTT_CONNECT,null,null);
            } catch (MqttException e) {
                e.printStackTrace();
            }
        }
    }
    /**
     * 判断网络是否连接
     */
    private static boolean isConnectIsNomarl() {
        if(mMsgMqttService!=null) {
            ConnectivityManager connectivityManager = (ConnectivityManager) mMsgMqttService.getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE);
            @SuppressLint("MissingPermission") NetworkInfo info = connectivityManager.getActiveNetworkInfo();
            if (info != null && info.isAvailable()) {
                String name = info.getTypeName();
                Log.i(TAG, "当前网络名称：" + name);
                return true;
            } else {
                Log.i(TAG, "没有可用网络");
                /*没有可用网络的时候，延迟3秒再尝试重连*/
                new Handler().postDelayed(() -> {
                    // doClientConnection();
                }, 3000);
                return false;
            }
        }else{
            return false;
        }
    }
    /**获得 Pseudo ID*/
    private static String getUniquePseudoID() {
        String serial;
        String m_szDevIDShort = "35" +
                Build.BOARD.length()%10+ Build.BRAND.length()%10 +
                Build.CPU_ABI.length()%10 + Build.DEVICE.length()%10 +
                Build.DISPLAY.length()%10 + Build.HOST.length()%10 +
                Build.ID.length()%10 + Build.MANUFACTURER.length()%10 +
                Build.MODEL.length()%10 + Build.PRODUCT.length()%10 +
                Build.TAGS.length()%10 + Build.TYPE.length()%10 +
                Build.USER.length()%10 ; //13 位
        try {
            serial = Build.class.getField("SERIAL").get(null).toString();
            //API>=9 使用serial号
            return new UUID(m_szDevIDShort.hashCode(), serial.hashCode()).toString();
        } catch (Exception exception) {
            //serial需要一个初始化,随意值
            serial = "serial";
        }
        //使用硬件信息拼凑出来的15位号码
        return new UUID(m_szDevIDShort.hashCode(), serial.hashCode()).toString();
    }
    /**
     * 连接回调
     */
    private static IMqttActionListener iMqttActionListener = new IMqttActionListener() {
        @Override
        public void onSuccess(IMqttToken arg0) {
            Log.i(TAG, "连接成功 ");
            broadcastUpdate(MQTT_CONNECT_SUCCESS,null,null);
//            try {
//                mqttAndroidClient.subscribe(PUBLISH_TOPIC, 2);//订阅主题，参数：主题、服务质
//            } catch (MqttException e) {
//                e.printStackTrace();
//            }
        }

        @Override
        public void onFailure(IMqttToken arg0, Throwable arg1) {
            arg1.printStackTrace();
            Log.i(TAG, "连接失败 ");
            broadcastUpdate(MQTT_CONNECT_FAIL,null,null);
            doClientConnection();//连接失败，重连（可关闭服务器进行模拟）
        }
    };
    //订阅主题的回调
    private static MqttCallback mqttCallback = new MqttCallback() {
        @Override
        public void connectionLost(Throwable cause) {
            Log.i(TAG, "连接断开 ");
            if(!userDisconnect) {
                broadcastUpdate(MQTT_DISCONNECT,null,null);
                doClientConnection();//连接断开，重连
            }else{
                userDisconnect=false;
            }
        }

        @Override
        public void messageArrived(String topic, MqttMessage message) {
            broadcastUpdate(MQTT_MESSAGE_ARRIVED,topic,message);
        }

        @Override
        public void deliveryComplete(IMqttDeliveryToken token) {

        }
    };
    /**本地绑定类*/
    class LocalBinder extends Binder {
        /**获取服务*/
        public  MsgMqttService getService() {
            return MsgMqttService.this;
        }
    }
    /**
     * 广播更新通知
     * @param action 事件类型
     */
    private static void broadcastUpdate( String action,String topic,MqttMessage message) {
        Intent intent = new Intent(action);
        if(message!=null) {
            intent.putExtra("topic",topic);
            intent.putExtra("id",message.getId());
            intent.putExtra("payload",message.getPayload());
            intent.putExtra("qos",message.getQos());
            intent.putExtra("retained",message.isRetained());
        }
        mMsgMqttService.sendBroadcast(intent);
    }
}
